package main

import (
	"crypto/sha256"
	"fmt"
	"github.com/boltdb/bolt"
	"log"
	"strconv"
	"strings"
	"time"
)

type BlockChain struct {
	Db *bolt.DB // 数据库操作句柄
	//Tail []byte  // 最后一个区块的哈希(从数据库中查找数据)
	Tail   string
	NodeID string // 标识该区块链对象所属节点
}

// 获取区块链对象(获取区块链操作句柄)
func NewBlockChain(nodeID string) *BlockChain {
	// 1 读取数据库
	db, err := bolt.Open(blockChainDB, 0600, nil)

	if err != nil {
		log.Panic("打开区块链数据库失败...")
	}

	defer db.Close()

	// 定义变量，存储完整区块的hash数据
	var tail []byte

	// 2 读取db 和 tail
	db.View(func(tx *bolt.Tx) error {
		// 找到抽屉，bucket
		bucket := tx.Bucket([]byte(blockBucket + nodeID))
		if bucket == nil {
			fmt.Printf("数据抽屉 < %s >不存在，请检查程序...", blockBucket+nodeID)
		}

		// 获取到完整区块的hash数据
		tail = bucket.Get([]byte(lastBlockHash))

		return nil
	})

	bc := &BlockChain{
		Db:     db,
		Tail:   string(tail),
		NodeID: nodeID,
	}

	currentNodeChainObj[nodeID] = bc
	return bc
}

/* 区块链方法 */
// 初始化系统区块链(创世区块等等)
// 2 将该创世区块存储到该编号节点的区块链中
func InitialBlockChain(nodeID string) {
	// 1 生成创世完整区块
	genesisFullBlock := GenesisFullBlock(nodeID)

	// 2 将该创世区块存储到该编号节点的区块链中
	// a 打开区块链数据库
	db, err := bolt.Open(blockChainDB, 0600, nil)

	if err != nil {
		log.Panic("打开区块链数据库失败...")
	}

	defer db.Close()

	// b 将创世完整区块写入区块链数据库中
	db.Update(func(tx *bolt.Tx) error {
		// 找到抽屉，bucket(如果没有，就创建)
		bucket := tx.Bucket([]byte(blockBucket + nodeID))
		if bucket == nil {
			// 创建抽屉
			bucket, err = tx.CreateBucket([]byte(blockBucket + nodeID))
			if err != nil {
				log.Panic("创建bucket失败...")
			}
		}

		// 写数据
		// key: hash  value: gob数据
		bucket.Put(genesisFullBlock.Hash, genesisFullBlock.Serialize())
		bucket.Put([]byte(lastBlockHash), genesisFullBlock.Hash)

		return nil
	})
}

/*
1) 交易:
a 判断节点是否该自己处理，如果是，将交易加入到自己的内存池中
b 判断该轮交易数量是否足够，如果足够，打包微块，并进行广播


2) 微块:
a 收到微块，加入到节点本地的微块池中
b 判断收到的微块数目是否足够，如果足够，打包完整区块，加入到本地区块链
*/

// 解析收到的命令行信息
func (bc *BlockChain) ParseCMDData(data []byte) {
	//fmt.Printf("节点 < %s > 收到的数据...:%s\n",bc.NodeID, data)
	//fmt.Println("哈哈哈")

	cmd := NewCMD(bc) // 生成命令行对象

	// 将数据进行拆分
	flags := strings.Split(string(data), "_") // 获取方法标识

	// 命令行功能
	switch flags[1] {
	case "getPubKey":
		cmd.getPubKey()
	case "getPrivateKey":
		cmd.getPrivateKey()
	case "getAllPubKey":
		cmd.getAllPubKey()
	case "printBlockInfo":
		// d_printBlockInfo_8000_0
		cmd.printBlockInfo(flags[3]) // 0 为创世区块标号
	case "printTransPool":
		// d_printTransPool_8000
		cmd.printTransPool()
	case "printTransNum":
		cmd.printTransNum()
	case "printTransAll":
		cmd.printTransAll()
	case "printMiniPool":
		cmd.printMiniPool()
	case "printHeight":
		cmd.printHeight()
	case "getUserInfo":
		cmd.getUserInfo(flags[3]) // flags[3]: 节点id
	}
}

// 2 获取接收到的数据并进行解析(交易、微块)
func (bc *BlockChain) ParseData(sourceData []byte) {
	// 进行标识符确认
	flag := string(sourceData[0])

	if string(flag) == "a" {
		// 对收到的数据进行解析
		// infos: ["a", "from", "to", "value"]
		infos := strings.Split(string(sourceData), "_") // 获取方法标识

		// 获取收到的数据，进行普通交易的组织
		//from := infos[1]
		//to := infos[2]
		// 为了方便书写，直接使用节点id，查找公钥
		//from, _ := GetNodeKey(infos[1])
		//to, _ := GetNodeKey(infos[2])
		from := fromPerson // TODO 结构优化，防止多次打开钱包
		to := toPerson

		value, _ := strconv.Atoi(infos[3])
		// a 先把该节点对应的交易次数+1
		currentTransNumLock.Lock() // 加锁
		currentTransNum[bc.NodeID]++
		currentAllTransNum[bc.NodeID]++ // 节点记录系统中发生过的总的交易数目
		currentTransNumLock.Unlock()    // 解锁

		// 拿到该该节点委员会的信息
		var nodeCommitteeID int // 拿到该节点所属委员会的ID
		var members []string    // 拿到该节点的同一委员会成员

		for key, value := range committeeMemberInfo {
			for _, item := range value {
				if item == bc.NodeID {
					nodeCommitteeID = key
					members = value
					break
				}
			}
		}

		// b 判断该节点是不是每一轮中的委员会领导者，如果不是，直接退出
		if bc.NodeID != members[0] {
			currentTransNumLock.Lock() // 加锁
			// 如果达到要求，将普通节点的每轮的交易次数置空
			if currentTransNum[bc.NodeID] > maxTransNumPerRound {
				delete(currentTransNum, bc.NodeID) // 将交易次数置空
			}
			currentTransNumLock.Unlock() // 解锁

			return
		}

		// c 计算该交易所属委员会编号
		// 生成普通交易
		norTrans := NewNorTransaction([]byte(from), []byte(to), uint64(value))
		txCommitteeID := norTrans.GetCommitteeID()

		// d 判断领导者节点的编号和交易编号是否相同，如果不同，直接退出
		// 如果相同，则将该交易添加到该委员会领导者的交易池中
		if nodeCommitteeID == int(txCommitteeID) {
			// 判断
			// e 将该交易加入到领导者节点的内存池中
			transMemPoolLock.Lock() // 加锁
			transMemPool[bc.NodeID] = append(transMemPool[bc.NodeID], norTrans)
			transMemPoolLock.Unlock() // 解锁

			fmt.Printf("委员会领导者 < %s > 处理交易，委员会编号 < %d >，< %s > 给 < %s > 转账 < %d > ...\n", bc.NodeID, nodeCommitteeID, from, to, value) // 打印输出
		}

		// f 判断系统中产生的交易数目是否达到最大值，如果是，则打包微块并进行转发

		currentTransNumLock.Lock()
		txsLen := currentTransNum[bc.NodeID] // 获取该节点的收到交易次数
		currentTransNumLock.Unlock()

		if txsLen >= maxTransNumPerRound {
			// 1) 将内存池中的交易组织成微块
			miniBlock := NewMiniBlock(bc.NodeID)

			// 2) 将微块进行转发
			miniBlock.SendMiniBlock(bc.NodeID)

			// 3) 将该节点的交易池清空
			transMemPoolLock.Lock()
			delete(transMemPool, bc.NodeID)
			// 4) 将每个节点收到的交易次数置空
			delete(currentTransNum, bc.NodeID)
			transMemPoolLock.Unlock()
		}

	} else if flag == "b" {
		// 分发交易
	} else if flag == "c" {
		// 转让交易
	} else if flag == "m" {

		// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		infos := strings.Split(string(sourceData), "$$$") // 使用特殊标识符进行切分 |

		if len(infos) != 3 {
			fmt.Println("目前长度为 >>>>:", len(infos))

			fmt.Println("微块分割长度不符合要求, 退出...")
			// log.Panicln("微块中的实验数据分割不等于3")
			return
		}

		miniBlock := MiniDeserialize([]byte(infos[0])[1:]) // 生成微块
		// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

		// 将微块放入内存中，判断微块数目是否足够触发生成完整区块
		nodeAcceptMiniNumLock.Lock() // 加锁
		nodeAcceptMiniNum[bc.NodeID] = append(nodeAcceptMiniNum[bc.NodeID], miniBlock)

		// 1 判断微块数目是否足够M (M为委员会数目)
		fmt.Printf("该< %s >节点当前收到 < %d > 微块数目\n", bc.NodeID, len(nodeAcceptMiniNum[bc.NodeID]))

		MiniNumLen := len(nodeAcceptMiniNum[bc.NodeID])
		nodeAcceptMiniNumLock.Unlock() // 解锁

		// 如果不满足微块重组条件，直接退出
		if MiniNumLen < committeeNum {
			return
		}

		// 1 如果长度为M，则说明收到M个微块，需要组织完整区块
		fmt.Printf("==========> < %s > 节点触发微块重组... <=========\n", bc.NodeID)

		// TODO 1  微块重组时间 only start >>>>>>>>>>>>>>>>>>>>>>>
		start := time.Now().UnixNano()
		bc.AddFullBlock()                // 触发添加完整区块功能
		go miniToFullTime(bc.NodeID, start) 
		// >>>>>>>>>>>>>>>>> 微块重组时间 only end >>>>>>>>>>>>>>>>>>>>>>>

		// TODO 2 微块重组全过程【not use this method ——>change to use txSpeed】 start >>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		// MiniToFullAllTime(bc.NodeID, infos[1], infos[2]) // 接受微块节点、发送微块节点、发送微块时间起始时间
		// >>>>>>>>>>>>>>>>>> 微块重组全过程 end >>>>>>>>>>>>>>>>>>>>>>>>>>>>>

		// TODO 3 交易tps start >>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		go TransSpeed(bc.NodeID, infos[1], infos[2])  // 考虑网络延迟
		go TransSpeedNoDelay(bc.NodeID, start)  // 不考虑网络延迟
		// >>>>>>>>>>>>>>>>>> 交易tps end >>>>>>>>>>>>>>>>>>>>>>>>>>>>>

		fmt.Printf("==========> < %s > 节点微块重组完成... <=========\n", bc.NodeID)

		nodeAcceptMiniNumLock.Lock() // 加锁
		// 2 将该节点的微块池清空
		delete(nodeAcceptMiniNum, bc.NodeID)
		nodeAcceptMiniNumLock.Unlock() // 解锁

		// TODO 完整区块组织完，触发下一轮共识
		consensus := NewConsensus(bc)
		consensus.StartConsensus() // 执行共识
	}
}

// 3 判断收到的微块个数是否足够 组装完整区块
// a) 将完整区块追加到自己本地的区块链
func (bc *BlockChain) AddFullBlock() {
	// 1 调用数据库，调用数据库获取中的数据(直接获取的数据: round、prevHash)
	db, err := bolt.Open(blockChainDB, 0600, nil)

	if err != nil {
		log.Panic("打开数据库失败")
	}

	defer db.Close()

	var miniTimeStamp []uint64 // 存放收到的微块时间戳
	//var maxIndex = 0           // 定义最大时间戳索引

	db.Update(func(tx *bolt.Tx) error {
		// a 查找bucket
		bucket := tx.Bucket([]byte(blockBucket + bc.NodeID))
		if bucket == nil {
			log.Panic("bucket不应该为空,请进行检查...")
		}

		// b 获取前一个完整区块
		fullBlockData := bucket.Get([]byte(bc.Tail))
		fullBlock := FullDeserialize(fullBlockData)
		// c 获取round、prevHash
		round := fullBlock.Round + 1
		prevHash := fullBlock.Hash
		prevAccountRoot := fullBlock.AccountRoot

		// 通过前账户树的根hash获取账户树结构  // 存储树先不进行考虑，只考虑账户树和交易树
		prevAccountTreeGob := bucket.Get(prevAccountRoot)
		accountTree := AccountDeserialize(prevAccountTreeGob)

		transTree := NewTransTree() // 生成交易树

		// 2 需要计算的数据: accountRoot、storageRoot
		// a 遍历该节点内存池中所有的微块，进行账户树的修改
		miniBlockList := nodeAcceptMiniNum[bc.NodeID]
		for _, item := range miniBlockList {
			// 补充: 添加微块的时间戳
			miniTimeStamp = append(miniTimeStamp, item.TimeStamp)
			// b 获取微块中的普通交易列表(目前分发交易和转让交易不处理)
			norTransList := item.TransTree.NorTransList
			// c 解析每一笔交易
			for _, obj := range norTransList {
				// --------对账户树进行修改--------
				from := obj.From // 转账人
				to := obj.To     // 收款人
				//count :'= obj.Count // 交易次数
				value := obj.Value // 交易额度

				// d 对上一轮的账户树进行修改
				// 1) 修改转账人的信息
				accountTree.UserInfoMap[string(from)].Balance -= value // 需要进行越界判断 这里传的是公钥，而不是节点
				accountTree.UserInfoMap[string(from)].Count++
				// 2) 修改收款人的信息
				accountTree.UserInfoMap[string(to)].Balance += value

				// ----------组织交易树---------
				transTree.NorTransList = append(transTree.NorTransList, norTransList...)
			}
		}

		// 获取最大的时间戳
		//for index, tmp := range miniTimeStamp {
		//	if tmp > miniTimeStamp[maxIndex] {
		//		maxIndex = index
		//	}
		//}

		// 取账户树的hash
		accountTreeGob := accountTree.Serialize()
		accountTreeRootHash := sha256.Sum256(accountTreeGob[:20]) // 这个问题无敌， (还需要将账户树进行存储)

		// 取交易树的hash
		transTreeRootHash := sha256.Sum256(transTree.Serialize())

		// 生成完整区块
		// round uint64, prevHash, accountRoot, storageRoot, transRoot, leaderList
		newFullBlock := NewFullBlock(
			round,
			prevHash,
			//miniTimeStamp[maxIndex], // TODO new: 完整区块时间戳问题
			12345789, // TODO new: 完整区块时间戳问题
			accountTreeRootHash[:],
			nil,
			transTreeRootHash[:],
			nil,
		)

		// 完整区块序列化
		newFullBlockHash := sha256.Sum256(newFullBlock.Serialize())

		// 将数据存入区块链中
		// a 账户树哈希 账户树
		bucket.Put(accountTreeRootHash[:], accountTreeGob)
		// b 交易树哈希 交易树
		bucket.Put(transTreeRootHash[:], transTree.Serialize())
		// c 完整区块哈希 完整区块
		bucket.Put(newFullBlockHash[:], newFullBlock.Serialize())
		// 修改区块链对象的索引
		bc.Tail = string(newFullBlockHash[:])
		// 修改系统中最后指针的索引
		bucket.Put([]byte(lastBlockHash), newFullBlockHash[:])
		return nil
	})
}
